package se.sics.kompics.tutorial.tutorial4.FELD;

import java.util.HashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.kth.ict.id2203.pp2p.PerfectPointToPointLink;
import se.kth.ict.id2203.pp2p.Pp2pSend;
import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Negative;
import se.sics.kompics.Positive;
import se.sics.kompics.address.Address;
import se.sics.kompics.timer.ScheduleTimeout;
import se.sics.kompics.timer.Timer;
import se.sics.kompics.tutorial.tutorial4.FELD.communication.FELDTimeout;
import se.sics.kompics.tutorial.tutorial4.FELD.communication.HeartBeatMessage;
import se.sics.kompics.tutorial.tutorial4.FELD.communication.LeaderPort;
import se.sics.kompics.tutorial.tutorial4.FELD.communication.Trust;

public class FELDImpl extends ComponentDefinition {
	
	private static final Logger logger = LoggerFactory.getLogger(FELDImpl.class);
	
	Negative<LeaderPort> leaderPort = provides(LeaderPort.class);
	Positive<PerfectPointToPointLink> pp2p = requires(PerfectPointToPointLink.class);
	Positive<Timer> timer = requires(Timer.class);
	
	private static final long TIME_DELAY = 500;
	private static final long DELTA = 1000;
	
	private Set<Address> neighbors;
	private Set<Address> candidates;
	private Address self;
	private Address leader;
	private long period;
	
	public FELDImpl() {
		subscribe(handleInit, control);
		subscribe(handleHeartbeatMessage, pp2p);
		subscribe(handleTimeout, timer);
	}
	
	Handler<FELDInit> handleInit = new Handler<FELDInit>() {
		
		@Override
		public void handle(FELDInit event) {
			self = event.getSelf();
			neighbors = event.getNeighbors();
			neighbors.add(self);
			leader = selectLeader(neighbors);
			trigger(new Trust(leader), leaderPort);
			logger.info("New leader: " + leader.toString());
			
			period = TIME_DELAY;
			for(Address n : neighbors) {
				trigger(new Pp2pSend(n, new HeartBeatMessage(self)), pp2p);
			}
			candidates = new HashSet<Address>();
			
			ScheduleTimeout timeout = new ScheduleTimeout(period);
			timeout.setTimeoutEvent(new FELDTimeout(timeout));
			trigger(timeout, timer);
		}
		
	};
	
	Handler<FELDTimeout> handleTimeout = new Handler<FELDTimeout>() {
		
		@Override
		public void handle(FELDTimeout event) {
			Address newLeader = selectLeader(candidates);
			if(!leader.equals(newLeader) && newLeader != null) {
				period += DELTA;
				leader = newLeader;
				trigger(new Trust(leader), leaderPort);
				logger.info("New leader: " + leader.toString());
			}
			
			for(Address n : neighbors) {
				trigger(new Pp2pSend(n, new HeartBeatMessage(self)), pp2p);
			}
			candidates.clear();
			
			ScheduleTimeout timeout = new ScheduleTimeout(period);
			timeout.setTimeoutEvent(new FELDTimeout(timeout));
			trigger(timeout, timer);
		}
	};
	
	Handler<HeartBeatMessage> handleHeartbeatMessage = new Handler<HeartBeatMessage>() {
		
		@Override
		public void handle(HeartBeatMessage event) {
			candidates.add(event.getSource());
		}
	};
	
	private Address selectLeader(Set<Address> candidates) {
		/*Address l = candidates.iterator().next();
        while(candidates.iterator().hasNext()) {
                Address tmp = candidates.iterator().next();
                if (tmp.getPort() < l.getPort()) {
                        l = tmp;
                }
        }*/
        Address l = self;
        for(Address tmp : candidates){
                if (tmp.getPort() < l.getPort()) {
                        l = tmp;
                }
        }
        return l;
	}
}
